/*
 * Ingenic libsysutils key implement.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/input.h>

#include <imp/imp_log.h>
#include <imp/imp_utils.h>
#include <sysutils/su_misc.h>

#define TAG						"SU_Key"
#define PATH_INPUT				"/dev/input"
#define SIZE_LONG				(8 * sizeof(long))
#define BITS_TO_LONGS(nr)		((nr + SIZE_LONG - 1) / SIZE_LONG)
#define TEST_TYPE(type, mask)	(mask[(type) / SIZE_LONG] & (1 << ((type) % SIZE_LONG)))

#define KEY_DISABLE_PATH "/sys/devices/platform/gpio-keys/disabled_keys"
#define KEY_PATH "/sys/devices/platform/gpio-keys/keys"
#define MAX_KEY_STRINGS_LEN 128
#define NR_MAX_KEYS 32

struct keys_map {
	unsigned code:16;
	unsigned disabled:16;
};

static struct keys_map *globle_keys = NULL;
static int g_nr_keys = 0;

int SU_Key_OpenEvent(void)
{
	DIR *dir;
	struct dirent *de;
	int ret;
	char path[BUFSIZ];

	dir = opendir(PATH_INPUT);
	if (dir == NULL) {
		IMP_LOG_ERR(TAG, "open input dir error\n");
		return -1;
	}

	while ((de = readdir(dir)) != NULL) {
		char name[80];

		int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
		if (fd < 0) {
			IMP_LOG_ERR(TAG, "openat error\n");
			continue;
		}

		ret = ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name);
		close(fd);

		if (ret < 1) {
			IMP_LOG_VERBOSE(TAG, "could not get device name for %s/%s: %s\n",
							PATH_INPUT, de->d_name, strerror(errno));
			continue;
		}

		if (!strcmp(name, "gpio-keys")) {
			sprintf(path, "%s/%s", PATH_INPUT, de->d_name);
			IMP_LOG_DBG(TAG, "GPIO key device : %s", path);
			break;
		}
	}

	closedir(dir);

	int evfd = open(path, O_RDONLY);
	if (evfd < 0) {
		IMP_LOG_ERR(TAG, "open %s error: %s\n", path, strerror(errno));
		return -1;
	}

	return evfd;
}

int SU_Key_CloseEvent(int evfd)
{
	return close(evfd);
}

int SU_Key_ReadEvent(int evfd, int *keyCode, SUKeyEvent *event)
{
	int ret;
	struct input_event in_event;

	while (1) {
		ret = read(evfd, &in_event, sizeof(struct input_event));
		if (ret <= 0) {
			IMP_LOG_ERR(TAG, "read key event error: %s\n", strerror(errno));
			return -1;
		}

		if (in_event.type != EV_KEY)
			continue;

		IMP_LOG_DBG(TAG, "Key:%d type:%d ", in_event.code & 0xff, in_event.type);
		if (in_event.value) {
			IMP_LOG_DBG(TAG, "KEY_PRESSED\n");
			*event = KEY_PRESSED;
		} else {
			IMP_LOG_DBG(TAG, "KEY_RELEASED\n");
			*event = KEY_RELEASED;
		}

		*keyCode = in_event.code & 0xff;
		break;
	}

	return 0;
}

static int load_globle_keys(void)
{
	int fd = open(KEY_PATH, O_RDONLY);
	if (fd < 0) {
		IMP_LOG_ERR(TAG, "open keys error:%s\n", strerror(errno));
		return -1;
	}
	char buf[MAX_KEY_STRINGS_LEN];
	int ret = read(fd, buf, MAX_KEY_STRINGS_LEN);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "read keys error:%s\n", strerror(errno));
		return -1;
	}

	close(fd);
	char *save_ptr = NULL;
	char *p;

	char *dpos = NULL;
	/* cancel some useless line */
	if ((dpos = strrchr(buf, '\n')) != NULL) {
		*dpos = '\0';
	}

	if ((dpos = strrchr(buf, '\r')) != NULL) {
		*dpos = '\0';
	}

	struct keys_map tmp_key_map[NR_MAX_KEYS];
	int nr_keys = 0;

	p = strtok_r(buf, ",", &save_ptr);
	if (p) {
		tmp_key_map[nr_keys].code = atoi(p);
		tmp_key_map[nr_keys].disabled = 0;
		nr_keys++;
		while ((p = strtok_r(NULL, ",", &save_ptr)) != NULL) {
			tmp_key_map[nr_keys].code = atoi(p);
			tmp_key_map[nr_keys].disabled = 0;
			nr_keys++;
			if (nr_keys >= NR_MAX_KEYS) {
				IMP_LOG_ERR(TAG, "Too many keys\n");
				return -1;
			}
		}

	}

	globle_keys = malloc(nr_keys * sizeof(struct keys_map));
	if (!globle_keys) {
		IMP_LOG_ERR(TAG, "malloc key_map mem error: %s\n", strerror(errno));
		return -1;
	}

	memcpy(globle_keys, tmp_key_map, nr_keys * sizeof(struct keys_map));
	g_nr_keys = nr_keys;

	IMP_LOG_DBG(TAG, "keys:");
	int i;
	for (i = 0; i < g_nr_keys; i++) {
		IMP_LOG_DBG(TAG, " (%d, %d)", globle_keys[i].code, globle_keys[i].disabled);
	}
	IMP_LOG_DBG(TAG, "Total %d keys\n", g_nr_keys);

	return 0;
}

static int update_disabled_keys(void)
{
	char buf[MAX_KEY_STRINGS_LEN], buf_disabled[MAX_KEY_STRINGS_LEN];
	char *ps = NULL, *pe = NULL;
	int ret = 0, i = 0, needdisablecnt = 0;

	memset(buf, 0, sizeof(buf));
	memset(buf_disabled, 0, sizeof(buf_disabled));

	for (i = 0; i < g_nr_keys; i++) {
		if (globle_keys[i].disabled) {
			char key[4];
			memset(key, 0, 4);
			sprintf(key, "%d,", globle_keys[i].code);
			strcat(buf, key);
		}
	}

	if (buf[0] == '\0') {
		goto out_point;
	}

	char *dpos = NULL;
	if ((dpos = strrchr(buf, ',')) != NULL) {
		*dpos = '\0';
		IMP_LOG_DBG(TAG, "disabled key buf:%s\n", buf);
	}

	int fd = open(KEY_DISABLE_PATH, O_RDWR);
	if (fd < 0) {
		IMP_LOG_ERR(TAG, "open %s error:%s\n", KEY_DISABLE_PATH, strerror(errno));
		goto err_open;
	}

	ret = read(fd, buf_disabled, MAX_KEY_STRINGS_LEN);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "read disabled_keys error: %s\n", strerror(errno));
		goto err_read_buf_disabled;
	}

	if ((dpos = strrchr(buf_disabled, '\n')) != NULL) {
		*dpos = '\0';
	}

	if ((dpos = strrchr(buf_disabled, '\r')) != NULL) {
		*dpos = '\0';
	}
	if (buf_disabled[0] == '\0') {
		strcat(buf_disabled, buf);
		goto write_buf_disabled_point;
	}

	for (ps = buf; ps != NULL; ps = pe + 1) {
		pe = strchr(ps, ',');
		if ((pe != NULL) && ((*pe) != '\0')) {
			*pe = '\0';
		}
		if (strstr(buf_disabled, ps) == NULL) {
			strcat(buf_disabled, ",");
			strcat(buf_disabled, ps);
			needdisablecnt++;
		}
		if (pe == NULL) {
			break;
		}
	}

	if (needdisablecnt == 0) {
		IMP_LOG_DBG(TAG, "%s(%d):disabled keys = %s, needdisablecnt = %d\n", __func__, __LINE__, buf_disabled, needdisablecnt);
		goto out_point_needdisablecnt_is_zero;
	}

write_buf_disabled_point:
	ret = write(fd, buf_disabled, strlen(buf) + 1); /* Don't forget '\0' */
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "write disabled_keys error: %s\n", strerror(errno));
		goto err_write_buf_disabled;
	}

	memset(buf_disabled, 0, sizeof(buf_disabled));
	ret = read(fd, buf_disabled, MAX_KEY_STRINGS_LEN);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "read back disabled_keys error: %s\n", strerror(errno));
		goto err_rereead_buf_disabled;
	}

	if ((dpos = strrchr(buf_disabled, '\n')) != NULL) {
		*dpos = '\0';
	}

	if ((dpos = strrchr(buf_disabled, '\r')) != NULL) {
		*dpos = '\0';
	}

	IMP_LOG_DBG(TAG, "%s(%d):disabled keys = %s\n", __func__, __LINE__, buf_disabled);

out_point_needdisablecnt_is_zero:
	close(fd);
out_point:
	return 0;

err_rereead_buf_disabled:
err_write_buf_disabled:
err_read_buf_disabled:
	close(fd);
err_open:
	return -1;
}

static int update_enabled_keys(void)
{
	char buf[MAX_KEY_STRINGS_LEN], buf_disabled[MAX_KEY_STRINGS_LEN];
	char *ps = NULL, *pe = NULL, *pstring = NULL;
	int ret = 0, i = 0, needenablecnt = 0;

	memset(buf, 0, sizeof(buf));
	memset(buf_disabled, 0, sizeof(buf_disabled));

	for (i = 0; i < g_nr_keys; i++) {
		if (!globle_keys[i].disabled) {
			char key[4];
			memset(key, 0, 4);
			sprintf(key, "%d,", globle_keys[i].code);
			strcat(buf, key);
		}
	}

	if (buf[0] == '\0') {
		goto out_point;
	}

	char *dpos = NULL;
	if ((dpos = strrchr(buf, ',')) != NULL) {
		*dpos = '\0';
		IMP_LOG_DBG(TAG, "enabled key buf:%s\n", buf);
	}

	int fd = open(KEY_DISABLE_PATH, O_RDWR);
	if (fd < 0) {
		IMP_LOG_ERR(TAG, "open %s error:%s\n", KEY_DISABLE_PATH, strerror(errno));
		goto err_open;
	}

	ret = read(fd, buf_disabled, MAX_KEY_STRINGS_LEN);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "read disabled_keys error: %s\n", strerror(errno));
		goto err_read_buf_disabled;
	}

	if ((dpos = strrchr(buf_disabled, '\n')) != NULL) {
		*dpos = '\0';
	}

	if ((dpos = strrchr(buf_disabled, '\r')) != NULL) {
		*dpos = '\0';
	}
	if (buf_disabled[0] == '\0') {
		goto out_point_buf_disabled_is_NULL;
	}

	for (ps = buf; ps != NULL; ps = pe + 1) {
		pe = strchr(ps, ',');
		if ((pe != NULL) && ((*pe) != '\0')) {
			*pe = '\0';
		}
		if ((pstring = strstr(buf_disabled, ps)) != NULL) {
			char *pendstring = strchr(pstring, ',');
			*pstring = '\0';
			if (pendstring != NULL) {
				strcat(buf_disabled, pendstring + 1);
			}
			needenablecnt++;
		}
		if (pe == NULL) {
			break;
		}
	}

	if (needenablecnt == 0) {
		IMP_LOG_DBG(TAG, "%s(%d):disabled keys = %s, needenablecnt = %d\n", __func__, __LINE__, buf_disabled, needenablecnt);
		goto out_point_needenablecnt_is_zero;
	}

	ret = write(fd, buf_disabled, strlen(buf) + 1); /* Don't forget '\0' */
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "write disabled_keys error: %s\n", strerror(errno));
		goto err_write_buf_disabled;
	}

	memset(buf_disabled, 0, sizeof(buf_disabled));
	ret = read(fd, buf_disabled, MAX_KEY_STRINGS_LEN);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "read back disabled_keys error: %s\n", strerror(errno));
		goto err_rereead_buf_disabled;
	}

	if ((dpos = strrchr(buf_disabled, '\n')) != NULL) {
		*dpos = '\0';
	}

	if ((dpos = strrchr(buf_disabled, '\r')) != NULL) {
		*dpos = '\0';
	}

	IMP_LOG_DBG(TAG, "%s(%d):disabled keys = %s, needenablecnt = %d\n", __func__, __LINE__, buf_disabled, needenablecnt);

out_point_needenablecnt_is_zero:
out_point_buf_disabled_is_NULL:
	close(fd);
out_point:
	return 0;

err_rereead_buf_disabled:
err_write_buf_disabled:
err_read_buf_disabled:
	close(fd);
err_open:
	return -1;
}

int SU_Key_EnableEvent(int keyCode)
{
	if (globle_keys == NULL) {
		if (load_globle_keys() < 0)
			goto error;
	}

	int i;
	for (i = 0; i < g_nr_keys; i++) {
		if (keyCode == globle_keys[i].code) {
			globle_keys[i].disabled = 0;
			break;
		}
	}

	if (update_enabled_keys() < 0)
		goto error;

	return 0;
error:
	IMP_LOG_ERR(TAG, "%s error\n", __func__);
	return -1;
}

int SU_Key_DisableEvent(int keyCode)
{
	if (globle_keys == NULL) {
		if (load_globle_keys() < 0)
			goto error;
	}

	int i;
	for (i = 0; i < g_nr_keys; i++) {
		if (keyCode == globle_keys[i].code) {
			globle_keys[i].disabled = 1;
			break;
		}
	}

	if (update_disabled_keys() < 0)
		goto error;

	return 0;
error:
	IMP_LOG_ERR(TAG, "%s error\n", __func__);
	return -1;
}
